/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package br.gov.cemaden.agent;

import br.gov.cemaden.agent.exception.ArgumentsException;
import br.gov.cemaden.agent.util.CalendarUtil;
import br.gov.cemaden.agent.util.HibernateUtil;
import br.gov.cemaden.library.dao.smp.LogAplicacaoDao;
import br.gov.cemaden.library.dao.staff.AplicacaoDao;
import br.gov.cemaden.library.dao.staff.ExecucaoDao;
import br.gov.cemaden.library.model.Usuario;
import br.gov.cemaden.library.model.smp.LogAplicacao;
import br.gov.cemaden.library.model.staff.Aplicacao;
import br.gov.cemaden.library.model.staff.Execucao;
import br.gov.cemaden.sendmail.Sendmail;
import br.gov.cemaden.sendmail.exception.SendmailConfigurationException;
import jade.core.Agent;
import jade.core.behaviours.TickerBehaviour;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.mail.MessagingException;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;

/**
 *
 * @author Alan
 */
public class AplicacaoMonitor extends Agent {

    private Path file;
    private Long executionPeriod, emailPeriod;
    private Map<Long, Long> sentmails;
    private Long databaseExceptionPeriod;
    
    private final Marker fatal;
    private final Logger logger;

    public AplicacaoMonitor() {
        if (sentmails == null) {
            sentmails = new HashMap<Long, Long>();
        }
        // executionPeriod default value is in milliseconds; it is received in seconds and validateArgs method parses the received velue to milliseconds
        executionPeriod = 60 * 1000L;
        // emailPeriod default value is in milliseconds; it is received in seconds and validateArgs method parses the received value to milliseconds
        emailPeriod = 10 * 60 * 1000L;
        // databaseExceptionPeriod = milliseconds
        databaseExceptionPeriod = (System.currentTimeMillis() - emailPeriod);
        
        fatal = MarkerFactory.getMarker("FATAL");
        this.logger = LoggerFactory.getLogger(AplicacaoMonitor.class);
    }

    @Override
    protected void setup() {

        System.out.println("Starting agent " + getAID().getName()); 
        logger.info("Starting agent " + getAID().getName());

        Object[] args = getArguments();
        if (args != null) {
            validateArgs(args);
        } else {
            logger.error(fatal, "The agent is expecting at least 1 (one) parameter to run properly.");
            throw new ArgumentsException("The agent is expecting at least 1 (one) parameter to run properly.");
        }

        addBehaviour(new TickerBehaviour(this, executionPeriod) {
            @Override
            @SuppressWarnings("empty-statement")
            protected void onTick() {

                logger.debug(
                        String.format("Starting %s for the %d time at %s.", 
                                AplicacaoMonitor.class.getName(), getTickCount(), new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()))
                );

                Session session = null;
                String exception = null;
                StringWriter sw = new StringWriter();
                PrintWriter pw = new PrintWriter(sw);

                /**
                 * Try to connect to database and establish one session
                 * If the attempt goes wrong, try to send an e-mail to the responsible for the agent
                 */
                
                try {
                    session = HibernateUtil.getSessionFactory().openSession();
                } catch (HibernateException ex) {
                    ex.printStackTrace(pw);
                    exception = sw.toString();
                } catch (ExceptionInInitializerError ex) {
                    ex.printStackTrace(pw);
                    exception = sw.toString();
                } catch (Throwable ex) {
                    ex.printStackTrace(pw);
                    exception = sw.toString();
                } finally {
                    if (session == null) {
                        if ( (System.currentTimeMillis() - databaseExceptionPeriod) >= emailPeriod){
                            String warning = "Agente: Erro durante a conexão com o banco de dados!!";
                            String body = String.format("%s%nO Agente %s ira tentar conectar-se novamente em %d segundos. "
                                    + "Caso não seja possível, um novo e-mail será enviado ao endereço de e-mail cadastrado.%n%n%s",
                                    warning, getAID().getName(), TimeUnit.MILLISECONDS.toSeconds(emailPeriod), exception);
                            try {
                                new Sendmail(warning, body, getFile()).send(); 
                            } catch (MessagingException ex) {
                                logger.error(String.format("Erro na tentativa de envio de e-mail: %s%n", ex));
                            } catch (FileNotFoundException ex) {
                                logger.error(String.format("Erro na tentativa de envio de e-mail: %s%n", ex));
                            } catch (IOException ex) {
                                logger.error(String.format("Erro na tentativa de envio de e-mail: %s%n", ex));
                            } catch (SendmailConfigurationException ex) {
                                logger.error(String.format("Erro na tentativa de envio de e-mail: %s%n", ex));
                            }
                            databaseExceptionPeriod = System.currentTimeMillis();
                        }
                    }
                }                                
                
                /**
                 * If connected to database
                 */
                if (session != null) {

                    try {
                        
                        AplicacaoDao aDao = new AplicacaoDao(session);
                        ExecucaoDao eDao = new ExecucaoDao(session);
                        
                        // Seleciona todas as aplicacoes e itera sobre elas
                        List<Aplicacao> aplicacoes = aDao.findAll();
                        
                        for (Aplicacao aplicacao : aplicacoes) {
                            
                            // Pega a última execução de uma aplicação
                            Execucao execucao = eDao.findLastByAplicacao(aplicacao);
                            
                            if(execucao!=null){                                                        
                                
                                // Se o tempo máximo sem execução de uma aplicação não estiver definido, defina-o como 15 minutos
                                Long tempo = (aplicacao.getTempo() == null) ? 15L : aplicacao.getTempo();
                                tempo = tempo * 60 * 1000;

                                // Se a datahora da última execução for menor que a datahora limite para a última execução
                                if( execucao.getFim().getTime() < ( System.currentTimeMillis() - tempo ) ){
                                    
                                    String output = null;
                                    
                                    // Tenta executar a aplicação caso tenha algum script cadastrado
                                    if(aplicacao.getScript()!= null){
                                    
                                        ProcessBuilder builder = new ProcessBuilder(aplicacao.getScript());

                                        try {
                                            builder.start();
                                        } catch (IOException ex) {
                                            ex.printStackTrace(pw);
                                            exception = sw.toString();
                                            output = String.format("%s", exception);
                                        }
                                    }                    
                
                                    if (!sentmails.containsKey(aplicacao.getId())) {
                                        sentmails.put(aplicacao.getId(), (System.currentTimeMillis() - emailPeriod));
                                    }
                                    
                                    if ( (System.currentTimeMillis() - sentmails.get(aplicacao.getId())) >= emailPeriod) {
                                        
                                        // Tempo, em segundos, que a aplicação está parada
                                        Long seconds = TimeUnit.MILLISECONDS.toSeconds( System.currentTimeMillis() - execucao.getFim().getTime() );
                                        
                                        String subject = String.format("WARNING! The application %s blew the timeout without being executed.", aplicacao.getNome()).trim();                                       
                                        
                                        String body = null;
                                        
                                        if(aplicacao.getMensagem() != null){
                                            
                                            body = aplicacao.getMensagem()
                                                    .replace(":nome", aplicacao.getNome())
                                                    .replace(":tempo", Long.toString(TimeUnit.MILLISECONDS.toMinutes(tempo)))
                                                    .replace(":inicio", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(execucao.getInicio()))
                                                    .replace(":fim", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(execucao.getFim()))
                                                    .replace(":decorrido", CalendarUtil.getFormattedElapsedTime(execucao.getFim(), new Date()));                                            
                                            if(aplicacao.getDescricao() != null)
                                                    body = body.replace(":descricao", aplicacao.getDescricao());                                                    
                                        } else {
                                        
                                            body = String.format("The application %s is not generating results since %s (%d seconds or %s of the last result until the present moment). %n"
                                                    + "The limit time defined to this application, without generating results, is of %d minutes (%d seconds).", 
                                                    aplicacao.getNome(), 
                                                    new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(execucao.getFim()), 
                                                    seconds, 
                                                    CalendarUtil.getFormattedElapsedTime(execucao.getFim(), new Date()),
                                                    TimeUnit.MILLISECONDS.toMinutes(tempo),
                                                    TimeUnit.MILLISECONDS.toSeconds(tempo)
                                            );
                                        }
                                        
                                        if(output != null){
                                            body = body.concat(String.format("%nThe agent tryied to start the application but it caused an exception: %n%s", output));
                                        }                                                                                                                       
                                        
                                        Set<Usuario> usuarios = new HashSet<Usuario>();
                                        Set<String> emails = new HashSet<String>();
                                        
                                        for (Usuario usuario : aplicacao.getUsuarios()) {
                                            usuarios.add(usuario);
                                            if(usuario.getEmail() != null)
                                                emails.add(usuario.getEmail());
                                        }
                                        
                                        try {
                                            if(emails.size() > 0){
                                                new Sendmail(subject, body, getFile())
                                                    .setRecipients(emails)
                                                    .send();
                                            }
                                        } catch (MessagingException ex) {
                                            ex.printStackTrace(pw);
                                            exception = sw.toString();
                                            body = String.format("%s%n%s", body, exception);
                                        } catch (FileNotFoundException ex) {
                                            ex.printStackTrace(pw);
                                            exception = sw.toString();
                                            body = String.format("%s%n%s", body, exception);
                                        } catch (IOException ex) {
                                            ex.printStackTrace(pw);
                                            exception = sw.toString();
                                            body = String.format("%s%n%s", body, exception);
                                        } catch (SendmailConfigurationException ex) {
                                            ex.printStackTrace(pw);
                                            exception = sw.toString();
                                            body = String.format("%s%n%s", body, exception);
                                        }
                                        finally {                                        
                                            // Grava o log da notificação
                                            LogAplicacao log = new LogAplicacao(aplicacao, body, new Date(), usuarios);
                                            new LogAplicacaoDao(session).insert(log);
                                        }                                        
                                        sentmails.put(aplicacao.getId(), System.currentTimeMillis());                                        
                                    }                                
                                }                            
                            } // #Fim - if(execucao != null)
                        } // #Fim - for(Aplicacao aplicacao : aplicacoes)

                        session.close();
                        
                    } catch (HibernateException e) {
                        logger.error("Exception when trying to execute AplicacaoMonitor", e);
                        e.printStackTrace(pw);
                        exception = sw.toString();
                        try {
                            new Sendmail("Exception when trying to execute AplicacaoMonitor", exception, file).send();                            
                        } catch (IOException ex) {
                            logger.error(String.format("Erro na tentativa de envio de e-mail: %s%n", ex));
                        } catch (MessagingException ex) {
                            logger.error(String.format("Erro na tentativa de envio de e-mail: %s%n", ex));
                        } catch (SendmailConfigurationException ex) {
                            logger.error(String.format("Erro na tentativa de envio de e-mail: %s%n", ex));
                        }
                    }
                }
            }

        });
    }

    @Override
    protected void takeDown() {
        System.out.println("The agent " + getAID().getName() + " is finishing.");
    }

    private void setFile(String filename) throws FileNotFoundException {
        this.file = Paths.get(filename);
        if (Files.notExists(file)) {
            throw new FileNotFoundException("File not found: " + file);
        }
    }

    private Path getFile() {
        return this.file;
    }

    private void validateArgs(Object[] args) {
        if (args[0] != null) {
            try {
                this.setFile(args[0].toString());
            } catch (FileNotFoundException ex) {
                logger.error(fatal, "The first argument " + args[0].toString() + " must be a valid file and was not found.", ex);
                throw new ArgumentsException("The first argument " + args[0].toString() + " must be a valid file and was not found.", ex);
            }
        } else {
            logger.error(fatal, "There is no parameter for the e-mail properties file.");
            throw new ArgumentsException("There is no parameter for the e-mail properties file.");
        }
        if (args.length > 1) {
            executionPeriod = (args[1] != null) ? Long.parseLong(args[1].toString()) * 1000 : executionPeriod;            
            if (args.length > 2) {
                if(args[2] != null){
                    Integer seconds = Integer.parseInt(args[2].toString());
                    if(seconds > TimeUnit.MILLISECONDS.toSeconds(emailPeriod)){
                        emailPeriod = seconds * 1000L;
                    }
                    else{
                        logger.error("The value of the third parameter (email period) was changed to " + TimeUnit.MILLISECONDS.toSeconds(emailPeriod) + " seconds.");
                    }
                }
            }
        }
    }
    
}
